/******************************************************************************/
/*                                                                            */
/*  Copyright (c) 2009: ngi-central.org                                       */
/*  All rights reserved.                                                      */
/*                                                                            */
/******************************************************************************/


/// \file
/// This file implements the bounding_box algorithm.

#ifndef __sgl_bounding_box_h__
#define __sgl_bounding_box_h__

#include <sgl/global_settings.h>
#include <sgl/conditions.h>
#include <sgl/geometric_primitive.h>
#include <sgl/point.h>
#include <sgl/box.h>
#include <sgl/polygon.h>
#include <boost/spirit/home/phoenix/bind/bind_member_function.hpp>
#include <boost/spirit/home/phoenix/object/static_cast.hpp>

namespace sgl {


/******************************************************************************/
/*                                                                            */
/* bounding_box                                                               */
/*                                                                            */
/******************************************************************************/

template <
	/// The geometric primitive. 
	typename E>
/// Get the bounding box.
//!
//! This function calculates the bounding box of a geometric primitive. The
//! bounding box is an axis-parallel box that tightly fits the primitive.
//!
//! /return The bounding box of the geometric primitive.
inline sgl::box<E::value_type> bounding_box(
	/// The primitive.
	E const& primitive)
{
	sgl::box<E::dimensions(), E::value_type> box = 
		sgl::box<E::dimensions(), E::value_type>();
	error_type_does_not_support_calculation_of_bounding_box(); (*) // see below
	return box;
}

// (*) If you get an error in this line, this means that your type does not
//     support the calculation of a bounding box. However, if you think it 
//     should support the calculation of a bounding box, you should provide an 
//     overload of the bounding_box algorithm, which is able to work with your 
//     type.


template <
	/// The dimensionality. 
	size_t D,
	/// The element type. 
	typename T>
/// Get the bounding box.
//!
//! This function calculates the bounding box of a point. The bounding box of a 
//! point is a box located at the position of the point with size zero.
//!
//! /return The bounding box of the point.
inline sgl::box<D,T> bounding_box(
	sgl::point<D,T> const& pt)
{
	return sgl::box<D,T>(pt, sgl::vector<D,T>());
}


template <
	/// The dimensionality. 
	size_t D,
	/// The element type. 
	typename T>
/// Get the bounding box.
//!
//! This function calculates the bounding box of a vector. The bounding box of a 
//! vector is a box located at position zero with the same size as the vector.
//!
//! /return The bounding box of the point.
inline sgl::box<D,T> bounding_box(
	sgl::vector<D,T> const& vect)
{
	return sgl::box<D,T>(sgl::point<D,T>(), vect);
}


template <
	/// The dimensionality. 
	size_t D,
	/// The element type. 
	typename T>
/// Get the bounding box.
//!
//! This function calculates the bounding box of a box. The bounding box of a 
//! box is the box itself.
//!
//! /return The bounding box of the box.
inline sgl::box<D,T> bounding_box(
	sgl::box<D,T> const& box)
{
	return box;
}


template <
	/// The element type. 
	typename T>
/// Get the bounding box.
//!
//! This function calculates the bounding box of a two dimensional polygon.
//!
//! /return The bounding box of the polygon.
inline sgl::box<2,T> bounding_box(
	sgl::polygon<2,T> const& poly)
{
	if (poly.size() == 0)
		return sgl::box<2,T>();
	else if (poly.size() == 1)
		return sgl::bounding_box(poly[0]);
	else
	{
		using namespace boost::phoenix;

		T const& (sgl::point<2,T>::*get_x)() const = &sgl::point<2,T>::x;
		T const& (sgl::point<2,T>::*get_y)() const = &sgl::point<2,T>::y;

		T minx = std::min_element(poly.begin(), poly.end(), 
			bind(get_x, arg_names::_1) < bind(get_x, arg_names::_2))
				->x();
		T maxx = std::max_element(poly.begin(), poly.end(), 
			bind(get_x, arg_names::_1) < bind(get_x, arg_names::_2))
				->x();
		T miny = std::min_element(poly.begin(), poly.end(), 
			bind(get_y, arg_names::_1) < bind(get_y, arg_names::_2))
				->y();
		T maxy = std::max_element(poly.begin(), poly.end(), 
			bind(get_y, arg_names::_1) < bind(get_y, arg_names::_2))
				->y();

		sgl::box<2,T> box(
			sgl::point<2,T>(minx, miny), 
			sgl::vector<2,T>(maxx-minx, maxy-miny));

		return box;
	}

	return sgl::box<2,T>();
}

template <
	/// The element type. 
	typename T>
/// Get the bounding box.
//!
//! This function calculates the bounding box of a three dimensional polygon.
//!
//! /return The bounding box of the polygon.
inline sgl::box<3,T> bounding_box(
	sgl::polygon<3,T> const& poly)
{
	if (poly.size() == 0)
		return sgl::box<3,T>();
	else if (poly.size() == 1)
		return sgl::bounding_box(poly[0]);
	else
	{
		using namespace boost::phoenix;

		T const& (sgl::point<3,T>::*get_x)() const = &sgl::point<3,T>::x;
		T const& (sgl::point<3,T>::*get_y)() const = &sgl::point<3,T>::y;
		T const& (sgl::point<3,T>::*get_z)() const = &sgl::point<3,T>::z;

		T minx = std::min_element(poly.begin(), poly.end(), 
			bind(get_x, arg_names::_1) < bind(get_x, arg_names::_2))
				->x();
		T maxx = std::max_element(poly.begin(), poly.end(), 
			bind(get_x, arg_names::_1) < bind(get_x, arg_names::_2))
				->x();
		T miny = std::min_element(poly.begin(), poly.end(), 
			bind(get_y, arg_names::_1) < bind(get_y, arg_names::_2))
				->y();
		T maxy = std::max_element(poly.begin(), poly.end(), 
			bind(get_y, arg_names::_1) < bind(get_y, arg_names::_2))
				->y();
		T minz = std::min_element(poly.begin(), poly.end(), 
			bind(get_z, arg_names::_1) < bind(get_z, arg_names::_2))
				->z();
		T maxz = std::max_element(poly.begin(), poly.end(), 
			bind(get_z, arg_names::_1) < bind(get_z, arg_names::_2))
				->z();

		sgl::box<3,T> box(
			sgl::point<3,T>(minx, miny, minz), 
			sgl::vector<3,T>(maxx-minx, maxy-miny, maxz-minz));

		return box;
	}

	return sgl::box<3,T>();
}


} // namespace sgl

#endif // __sgl_bounding_box_h__
